/**
 * 
 */
package io.gitee.falllee.dao.core;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.calcite.linq4j.Linq4j;
import org.apache.ibatis.cursor.Cursor;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import io.gitee.falllee.common.page.PageRows;
import io.gitee.falllee.common.util.LogUtils;
import io.gitee.falllee.dao.annotation.Column;
import io.gitee.falllee.dao.annotation.Table;
import io.gitee.falllee.dao.cache.DataCache;
import io.gitee.falllee.dao.config.DaoSourceContextHolder;
import io.gitee.falllee.dao.enums.DataSourceEnum;
import io.gitee.falllee.dao.enums.QueryModeEnum;
import io.gitee.falllee.dao.model.DaoConst;
import io.gitee.falllee.dao.model.DaoException;
import io.gitee.falllee.dao.persistence.DataMapper;
import io.gitee.falllee.dao.util.CommonUtils;
import io.gitee.falllee.dao.util.ConvertUtils;
import io.gitee.falllee.dao.util.DaoUtils;
import io.gitee.falllee.dao.util.FilterPlus;

/**
 * @author batty
 * @date 2022-06-27
 *
 */
public class SelectBuilder<T> extends BaseBuilder {

    private Class<T> clazz;
    
    private DataCache dataCache;
    
    private DataMapper dataMapper;
    
    private CacheBuilder<T> cacheBuilder;

    SelectBuilder(Dao parent, Class<T> clazz) {
        super(parent.getDaoConfig());
        this.clazz = clazz;
        this.dataCache = parent.getDataCache();
        this.dataMapper = parent.getDataMapper();
        this.cacheBuilder = parent.cacheBuilder(clazz);
    }

    public SelectFieldBuilder<T> field(String filedName) {
        return new SelectFieldBuilder<>(this, daoConfig, filedName);
    }

    public SelectMultiFieldBuilder<T> multiField() {
        return new SelectMultiFieldBuilder<>(this, daoConfig);
    }

    public SelectFilterBuilder<T> filter(FilterPlus filter) {
        return new SelectFilterBuilder<>(this, daoConfig, filter);
    }

    public SelectCountBuilder<T> count(FilterPlus filter) {
        return new SelectCountBuilder<>(this, daoConfig, filter);
    }

    public SelectGroupBuilder<T> group(FilterPlus filter, String field, String group) {
        return new SelectGroupBuilder<>(this, daoConfig, filter, field, group);
    }

    public <V> SelectMinBuilder<T, V> min(FilterPlus filter, String field, Class<V> clazzV) {
        return new SelectMinBuilder<>(this, daoConfig, filter, field, clazzV);
    }

    public <V> SelectMaxBuilder<T, V> max(FilterPlus filter, String field, Class<V> clazzV) {
        return new SelectMaxBuilder<>(this, daoConfig, filter, field, clazzV);
    }

    public <V> SelectSumBuilder<T, V> sum(FilterPlus filter, String field, Class<V> clazzV) {
        return new SelectSumBuilder<>(this, daoConfig, filter, field, clazzV);
    }

    public <V> SelectDistinctBuilder<T, V> distinct(FilterPlus filter, String field, Class<V> clazzV) {
        return new SelectDistinctBuilder<>(this, daoConfig, filter, field, clazzV);
    }

    public T selectByKey(Object keyValue) {
        Table table = DaoUtils.getTable(clazz);
        // 如果启用缓存，先从缓存取数据
        boolean enableCache = cacheBuilder.enableCache(table);
        if (enableCache) {
            T t = dataCache.getEntityByKeyValue(keyValue, clazz);
            if (t != null) {
                DaoSourceContextHolder.setDaoSource(DataSourceEnum.CACHE.getSource());
                return t;
            }
        }
        String tableName = table.value();
        String keyField = DaoUtils.getKeyField(clazz);
        String fields = getSelectFields(clazz);
        Map<String, Object> mapResult = selectByKey(tableName, fields, keyField, keyValue);
        T t = ConvertUtils.map2Entity(mapResult, clazz);
        // 如果启用缓存，则写入缓存数据
        if (enableCache) {
            dataCache.setEntityByKeyValue(keyValue, t, table.expireTime());
        }
        DaoSourceContextHolder.setDaoSource(DataSourceEnum.DB.getSource());
        return t;
    }

    private String getSelectFields(Class<T> clazz) {
        String field = "";
        StringBuilder sbField = new StringBuilder();
        List<Column> columns = DaoUtils.getOrderColumns(clazz);
        for (Column col : columns) {
            String columnName = getColumnValue(col);
            String colQuotes = getQuoteField(columnName);
            if (!StringUtils.hasLength(columnName) || sbField.toString().contains(colQuotes)) {
                continue;
            }
            sbField.append(colQuotes);
            sbField.append(DaoConst.COMMA);
        }
        if (sbField.length() > 0) {
            field = sbField.substring(0, sbField.length() - 1);
        } else {
            throw new DaoException(DaoConst.NO_FIELDS_MAPPING);
        }
        return field;
    }

    private Map<String, Object> selectByKey(String tableName, @Nullable String fields, String keyField,
            Object keyValue) {
        Map<String, Object> map = new HashMap<>(16);
        if (!StringUtils.hasLength(tableName)) {
            throw new DaoException("table name cannot be null");
        }
        map.put(DaoConst.TABLE_NAME, tableName);
        if (!StringUtils.hasLength(keyField)) {
            throw new DaoException("key field cannot be null");
        }
        keyField = getQuoteField(keyField);
        map.put(DaoConst.KEY_FIELD, keyField);
        if (ObjectUtils.isEmpty(keyValue)) {
            throw new DaoException("key value cannot be null");
        }
        map.put(DaoConst.KEY_VALUE, keyValue);
        if (!StringUtils.hasLength(fields)) {
            fields = DaoConst.ASTERISK;
        }
        map.put(DaoConst.FIELD, fields);
        setEnableFlag(map, true);
        return dataMapper.selectByKey(map);
    }

    public List<T> selectByKeyList(List<? extends Object> list) {
        List<? extends Object> tempList = Linq4j.asEnumerable(list).where(p -> !ObjectUtils.isEmpty(p)).distinct()
                .toList();
        if (CollectionUtils.isEmpty(tempList)) {
            return new ArrayList<>();
        }
        Table table = DaoUtils.getTable(clazz);
        List<T> resultList = new ArrayList<>();
        Method keyGetMethod = DaoUtils.getKeyGetMethod(clazz);
        List<Object> unCachedKeyValueList = new ArrayList<>();
        boolean enableCache = cacheBuilder.enableCache(table);
        // 优先从缓存取数据
        if (enableCache) {
            resultList = dataCache.getEntityListByKeyValueList(tempList, unCachedKeyValueList, clazz);
            DaoSourceContextHolder.setCacheCount(resultList.size());
            if (CollectionUtils.isEmpty(unCachedKeyValueList)) {
                return resultList;
            }
        } else {
            unCachedKeyValueList.addAll(tempList);
        }
        String keyField = keyGetMethod.getAnnotation(Column.class).value();
        List<T> dbList = selectByFieldList(unCachedKeyValueList, keyField, FilterPlus.build(), "");
        if (enableCache && !CollectionUtils.isEmpty(dbList)) {
            // 写入缓存
            dataCache.setEntityList(dbList, table.expireTime());
        }
        DaoSourceContextHolder.setDbCount(dbList.size());
        resultList.addAll(dbList);
        return resultList;
    }

    <V> List<T> selectByFieldList(List<V> list, String fieldName, FilterPlus filterPlus, String sort, Class<?>... params) {
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        List<V> tempList = Linq4j.asEnumerable(list).where(p -> !ObjectUtils.isEmpty(p)).distinct().toList();
        if (CollectionUtils.isEmpty(tempList)) {
            return new ArrayList<>();
        }
        if (!StringUtils.hasLength(fieldName)) {
            throw new DaoException("field name can not be null");
        }
        Table table = DaoUtils.getTable(clazz);
        List<T> resultList = new ArrayList<>();
        String keyField = DaoUtils.getKeyField(clazz);
        Method getMethod = DaoUtils.getMethodByColumnValue(clazz, fieldName);
        // 从缓存获取数据,主键会独立缓存，无需重复缓存
        boolean blCache = !keyField.equalsIgnoreCase(fieldName) && cacheBuilder.enableCache(table);
        String filter = filterPlus.getFilter();
        if (blCache) {
            List<Object> unCachedKeyValueList = new ArrayList<>();
            resultList = dataCache.getEntityListByFieldList(fieldName, filter, sort, tempList, unCachedKeyValueList,
                    clazz);
            // 根据缓存没有的主键重新查询
            if (!CollectionUtils.isEmpty(unCachedKeyValueList)) {
                resultList.addAll(selectByKeyList(unCachedKeyValueList));
            }
            DaoSourceContextHolder.setCacheCount(resultList.size());
            removeCachedData(resultList, getMethod, tempList);
            if (CollectionUtils.isEmpty(tempList)) {
                return resultList;
            }
        }
        Map<String, Object> map = filterPlus.getParamMap();
        String tableName = table.value();
        String field = getSelectFields(clazz);
        map.put(DaoConst.TABLE_NAME, tableName);
        map.put(DaoConst.FIELD, field);
        map.put(DaoConst.FIELD_NAME, fieldName);
        map.put(DaoConst.FILTER, filterPlus.getParamFilter());
        map.put(DaoConst.SORT, sort);
        setEnableFlag(map, true);
        // 每次查询的个数最大为2000个
        int length = tempList.size();
        int num = length / DaoConst.IN_MAX_SIZE;
        int left = length % DaoConst.IN_MAX_SIZE;
        num = left > 0 ? num + 1 : num;
        List<T> dbList = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            List<V> subList = CommonUtils.getSubList(tempList, DaoConst.IN_MAX_SIZE, i);
            map.put(DaoConst.FIELD_VALUE_LIST, subList);
            List<Map<String, Object>> mapResult = dataMapper.selectByFieldList(map);
            List<T> subDbList = ConvertUtils.mapList2EntityList(mapResult, clazz);
            dbList.addAll(subDbList);
        }
        // 写入缓存
        if (blCache) {
            dataCache.setEntityListByField(fieldName, filter, sort, dbList, getMethod, table.expireTimeList(),
                    params);
        }
        DaoSourceContextHolder.setDbCount(dbList.size());
        resultList.addAll(dbList);
        return resultList;
    }

    private <V> void removeCachedData(List<T> resultList, Method getMethod, List<V> tempList) {
        if (CollectionUtils.isEmpty(tempList)) {
            return;
        }
        List<String> strList = tempList.stream().map(String::valueOf).collect(Collectors.toList());
        List<String> resultStrList = resultList.stream().map(p -> String.valueOf(DaoUtils.getValue(p, getMethod)))
                .distinct().collect(Collectors.toList());
        for (String result : resultStrList) {
            int index = strList.indexOf(result);
            if (index > -1) {
                tempList.remove(index);
                strList.remove(index);
            }
        }
    }

    public List<T> selectByMultiFieldList(List<LinkedHashMap<String, Object>> list, FilterPlus filterPlus, String sort,
            Class<?>... params) {
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        List<LinkedHashMap<String, Object>> tempList = Linq4j.asEnumerable(list).where(p -> !ObjectUtils.isEmpty(p))
                .distinct().toList();
        if (CollectionUtils.isEmpty(tempList)) {
            return new ArrayList<>();
        }
        Set<String> fieldSet = tempList.get(0).keySet();
        String multiFieldName = StringUtils.collectionToCommaDelimitedString(fieldSet);
        Table table = DaoUtils.getTable(clazz);
        List<T> resultList = new ArrayList<>();
        // 从缓存获取数据,主键会独立缓存，无需重复缓存
        Map<String, Method> methodMap = new HashMap<>(16);
        boolean blCache = cacheBuilder.enableCache(table);
        String filter = filterPlus.getFilter();
        if (blCache) {
            for (String fieldName : multiFieldName.split(DaoConst.SPLIT_COMMA)) {
                Method getMethod = DaoUtils.getMethodByColumnValue(clazz, fieldName);
                methodMap.put(fieldName, getMethod);
            }
            resultList = getEntityListByMultiFieldListFromCache(methodMap, tempList, filter, sort, clazz,
                    multiFieldName);
            DaoSourceContextHolder.setCacheCount(resultList.size());
            if (CollectionUtils.isEmpty(tempList)) {
                return resultList;
            }
        }
        Map<String, Object> map = filterPlus.getParamMap();
        String tableName = table.value();
        String field = getSelectFields(clazz);
        map.put(DaoConst.TABLE_NAME, tableName);
        map.put(DaoConst.FIELD, field);
        map.put(DaoConst.FIELD_NAME, String.format("(%s)", multiFieldName));
        map.put(DaoConst.FILTER, filterPlus.getParamFilter());
        map.put(DaoConst.SORT, sort);
        setEnableFlag(map, true);
        // 每次查询的个数最大为2000个
        int length = tempList.size();
        int num = length / DaoConst.IN_MAX_SIZE;
        int left = length % DaoConst.IN_MAX_SIZE;
        num = left > 0 ? num + 1 : num;
        List<T> dbList = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            List<LinkedHashMap<String, Object>> subList = CommonUtils.getSubList(tempList, DaoConst.IN_MAX_SIZE, i);
            map.put(DaoConst.MULTI_FIELD_VALUE_LIST, subList);
            List<Map<String, Object>> mapResult = dataMapper.selectByFieldList(map);
            List<T> subDbList = ConvertUtils.mapList2EntityList(mapResult, clazz);
            dbList.addAll(subDbList);
        }
        // 写入缓存
        if (blCache) {
            dataCache.setEntityListByMultiField(methodMap, multiFieldName, filter, sort, dbList,
                    table.expireTimeList(), params);
        }
        DaoSourceContextHolder.setDbCount(dbList.size());
        resultList.addAll(dbList);
        return resultList;
    }

    private List<T> getEntityListByMultiFieldListFromCache(Map<String, Method> methodMap,
            List<LinkedHashMap<String, Object>> list, String filter, String sort, Class<T> clazz,
            String multiFieldName) {
        List<Object> unCachedKeyValueList = new ArrayList<>();
        List<T> resultList = dataCache.getEntityListByMultiFieldList(multiFieldName, filter, sort, list,
                unCachedKeyValueList, clazz);
        List<Map<String, Object>> fieldValueList = Linq4j.asEnumerable(resultList).select(p -> {
            Map<String, Object> map = new LinkedHashMap<>();
            for (String field : multiFieldName.split(DaoConst.SPLIT_COMMA)) {
                Object val;
                val = DaoUtils.getValue(p, methodMap.get(field));
                if (val != null) {
                    map.put(field, val);
                }
            }
            return map;
        }).distinct().toList();
        // 根据缓存没有的主键重新查询
        if (!CollectionUtils.isEmpty(unCachedKeyValueList)) {
            resultList.addAll(selectByKeyList(unCachedKeyValueList));
        }
        removeCacheFieldValue(list, fieldValueList);
        return resultList;
    }

    private void removeCacheFieldValue(List<LinkedHashMap<String, Object>> list,
            List<Map<String, Object>> fieldValueList) {
        for (int i = list.size() - 1; i >= 0; i--) {
            LinkedHashMap<String, Object> map = list.get(i);
            int count = Linq4j.asEnumerable(fieldValueList).count(p -> {
                Set<String> keys = map.keySet();
                for (String key : keys) {
                    boolean bl = map.get(key) != null && p.get(key) != null
                            && map.get(key).toString().equals(p.get(key).toString());
                    if (!bl) {
                        return false;
                    }
                }
                return true;
            });
            if (count > 0) {
                list.remove(map);
            }
        }
    }

    List<T> select(FilterPlus filterPlus, String sort, Long page, Long rows, QueryModeEnum queryMode, Class<?>... params) {
        List<T> list;
        Table table = DaoUtils.getTable(clazz);
        // 查询模式：如果rows<=100使用默认查询
        if (rows == null || rows <= 100) {
            queryMode = QueryModeEnum.DEFAULT;
        }
        boolean defaultQueryMode = QueryModeEnum.DEFAULT.equals(queryMode);
        // 如果启用缓存，从缓存取数据；如果使用流式查询和游标查询，不使用缓存
        boolean enableCache = cacheBuilder.enableCache(table) && defaultQueryMode;
        String filter = filterPlus.getFilter();
        if (enableCache) {
            List<Object> unCachedKeyValueList = new ArrayList<>();
            list = dataCache.getEntityList(filter, sort, page, rows, unCachedKeyValueList, clazz);
            // 根据缓存没有的主键重新查询
            if (!CollectionUtils.isEmpty(unCachedKeyValueList)) {
                list.addAll(selectByKeyList(unCachedKeyValueList));
            }
            if (!CollectionUtils.isEmpty(list)) {
                DaoSourceContextHolder.setCacheCount(list.size());
                return list;
            }
        }
        String tableName = table.value();
        String fields = getSelectFields(clazz);
        Method[] methods = clazz.getMethods();
        if (defaultQueryMode) {
            List<Map<String, Object>> mapResult = select(tableName, fields, filterPlus, sort, page, rows, methods);
            list = ConvertUtils.mapList2EntityList(mapResult, clazz);
        } else {
            list = selectByMode(tableName, fields, filterPlus, sort, page, rows, methods, queryMode);
        }
        // 如果启用缓存，设置缓存数据
        if (enableCache) {
            dataCache.setEntityList(filter, sort, page, rows, list, table.expireTimeList(), params);
        }
        DaoSourceContextHolder.setDbCount(list.size());
        return list;
    }

    private List<T> selectByMode(String tableName, String fields, FilterPlus filterPlus, String sort, Long page,
            Long rows, Method[] methods, QueryModeEnum queryMode) {
        Map<String, Object> map = getQueryMap(tableName, fields, filterPlus, sort, page, rows);
        Cursor<Map<String, Object>> cursor = null;
        try {
            switch (queryMode) {
            case STREAMING:
                cursor = dataMapper.selectStreaming(map);
                break;
            case CURSOR:
                cursor = dataMapper.selectCursor(map);
                break;
            default:
                return Collections.emptyList();
            }
            List<T> list = new ArrayList<>();
            Iterator<Map<String, Object>> iterator = cursor.iterator();
            while (iterator.hasNext()) {
                Map<String, Object> resultMap = iterator.next();
                list.add(ConvertUtils.map2Entity(resultMap, clazz));
            }
            return list;
        } finally {
            if (cursor != null) {
                try {
                    cursor.close();
                } catch (IOException e) {
                    LogUtils.logException(e);
                }
            }
        }
    }

    private List<Map<String, Object>> select(String tableName, String fields, FilterPlus filterPlus, String sort, Long page,
            Long rows, Method[] methods) {
        Map<String, Object> map = getQueryMap(tableName, fields, filterPlus, sort, page, rows);
        return dataMapper.select(map);
    }

    private Map<String, Object> getQueryMap(String tableName, String fields, FilterPlus filterPlus, String sort,
            Long page, Long rows) {
        Map<String, Object> map = filterPlus.getParamMap();
        map.put(DaoConst.DB_TYPE, daoConfig.getDbType());
        map.put(DaoConst.TABLE_NAME, tableName);
        map.put(DaoConst.FIELD, fields);
        map.put(DaoConst.FILTER, filterPlus.getParamFilter());
        map.put(DaoConst.SORT, sort);
        if (page == null) {
            page = 1L;
        }
        if (rows == null) {
            rows = 10L;
        }
        Long start = (page - 1) * rows;
        map.put(DaoConst.START, start);
        map.put(DaoConst.ROWS, rows);
        map.put(DaoConst.END, start + rows);
        setEnableFlag(map, true);
        return map;
    }

    Long selectCount(FilterPlus filterPlus, String field, Class<?>... params) {
        Table table = DaoUtils.getTable(clazz);
        boolean enableCache = cacheBuilder.enableCache(table);
        String filter = filterPlus.getFilter();
        if (enableCache) {
            Long count = dataCache.getEntityCount(filter, field, clazz);
            if (count > 0) {
                DaoSourceContextHolder.setDaoSource(DataSourceEnum.CACHE.getSource());
                return count;
            }
        }
        Map<String, Object> map = filterPlus.getParamMap();
        String tableName = table.value();
        map.put(DaoConst.TABLE_NAME, tableName);
        map.put(DaoConst.FIELD, field);
        map.put(DaoConst.FILTER, filterPlus.getParamFilter());
        setEnableFlag(map, true);
        Long count = dataMapper.selectCount(map);
        if (count > 0 && enableCache) {
            dataCache.setEntityCount(filter, field, count, clazz, table.expireTimeList(), params);
        }
        DaoSourceContextHolder.setDaoSource(DataSourceEnum.DB.getSource());
        return count;
    }

    PageRows<T> selectPage(FilterPlus filterPlus, String sort, Long page, Long rows, QueryModeEnum queryMode,
            Class<?>... params) {
        List<T> list = select(filterPlus, sort, page, rows, queryMode, params);
        Long count = selectCount(filterPlus, null, params);
        PageRows<T> pageRows = new PageRows<>();
        page = (page == null || page <= 0) ? 1L : page;
        pageRows.setCurrent(page);
        pageRows.setTotal(count);
        pageRows.setRows(list);
        return pageRows;
    }

    List<Map<String, Object>> selectGroup(FilterPlus filterPlus, String field, String group, String sort, Long page, Long rows,
            Class<?>... params) {
        if (!StringUtils.hasLength(field) || !StringUtils.hasLength(group)) {
            throw new DaoException("field or group cannot be empty");
        }
        Table table = DaoUtils.getTable(clazz);
        // 如果启用缓存，从缓存取数据
        boolean enableCache = cacheBuilder.enableCache(table);
        String filter = filterPlus.getFilter();
        if (enableCache) {
            List<Map<String, Object>> list = dataCache.getEntityGroupList(filter, field, group, sort, page, rows, clazz);
            if (!CollectionUtils.isEmpty(list)) {
                DaoSourceContextHolder.setDaoSource(DataSourceEnum.CACHE.getSource());
                return list;
            }
        }
        Map<String, Object> map = filterPlus.getParamMap();
        map.put(DaoConst.DB_TYPE, daoConfig.getDbType());
        String tableName = table.value();
        map.put(DaoConst.TABLE_NAME, tableName);
        map.put(DaoConst.FIELD, field);
        map.put(DaoConst.FILTER, filterPlus.getParamFilter());
        map.put(DaoConst.GROUP, group);
        map.put(DaoConst.SORT, sort);
        if (page == null) {
            page = 1L;
        }
        if (rows == null) {
            rows = 10L;
        }
        Long start = (page - 1) * rows;
        map.put(DaoConst.START, start);
        map.put(DaoConst.ROWS, rows);
        setEnableFlag(map, true);
        List<Map<String, Object>> mapResult = dataMapper.selectGroup(map);
        if (!CollectionUtils.isEmpty(mapResult) && enableCache) {
            dataCache.setEntityGroupList(filter, field, group, sort, page, rows, mapResult, clazz, table.expireTimeList(),
                    params);
        }
        DaoSourceContextHolder.setDaoSource(DataSourceEnum.DB.getSource());
        return mapResult;
    }

    private <V> V selectAggregate(FilterPlus filterPlus, String aggregate, String field, Class<V> clazzV, Class<?>... params) {
        if (!StringUtils.hasLength(field)) {
            throw new DaoException(DaoConst.FIELD_CAN_NOT_BE_EMPTY);
        }
        Table table = DaoUtils.getTable(clazz);
        boolean enableCache = cacheBuilder.enableCache(table);
        String filter = filterPlus.getFilter();
        if (enableCache) {
            V v = dataCache.getEntityAggregate(filter, aggregate, field, clazz, clazzV);
            if (v != null) {
                DaoSourceContextHolder.setDaoSource(DataSourceEnum.CACHE.getSource());
                return v;
            }
        }
        Map<String, Object> map = filterPlus.getParamMap();
        String tableName = table.value();
        map.put(DaoConst.AGGREGATE, aggregate);
        map.put(DaoConst.TABLE_NAME, tableName);
        map.put(DaoConst.FIELD, field);
        map.put(DaoConst.FILTER, filterPlus.getParamFilter());
        setEnableFlag(map, true);
        Object obj = dataMapper.selectAggregate(map);
        V v = clazzV.cast(obj);
        if (v != null && enableCache) {
            dataCache.setEntityAggregate(filter, aggregate, field, v, clazz, table.expireTimeList(), params);
        }
        DaoSourceContextHolder.setDaoSource(DataSourceEnum.DB.getSource());
        return v;
    }

    <V> V selectMax(FilterPlus filterPlus, String field, Class<V> clazzV, Class<?>... params) {
        return selectAggregate(filterPlus, DaoConst.MAX, field, clazzV, params);
    }

    <V> V selectMin(FilterPlus filterPlus, String field, Class<V> clazzV, Class<?>... params) {
        return selectAggregate(filterPlus, DaoConst.MIN, field, clazzV, params);
    }

    <V> V selectSum(FilterPlus filterPlus, String field, Class<V> clazzV, Class<?>... params) {
        return selectAggregate(filterPlus, DaoConst.SUM, field, clazzV, params);
    }

    <V> List<V> selectDistinct(FilterPlus filterPlus, String field, Class<V> clazzV, Class<?>... params) {
        if (!StringUtils.hasLength(field)) {
            throw new DaoException(DaoConst.FIELD_CAN_NOT_BE_EMPTY);
        }
        Table table = DaoUtils.getTable(clazz);
        boolean enableCache = cacheBuilder.enableCache(table);
        String[] arr = field.split(DaoConst.COMMA);
        filterPlus.addBrackets().add(p -> {
            for (int i = 0; i < arr.length; i++) {
                p.addIsNotNull(arr[i]);
            }
        });
        String filter = filterPlus.getFilter();
        if (enableCache) {
            List<V> list = dataCache.getEntityDistinct(filter, field, clazz);
            if (!CollectionUtils.isEmpty(list)) {
                DaoSourceContextHolder.setDaoSource(DataSourceEnum.CACHE.getSource());
                return list;
            }
        }
        String tableName = table.value();
        Map<String, Object> map = filterPlus.getParamMap();
        map.put(DaoConst.TABLE_NAME, tableName);
        map.put(DaoConst.FIELD, field);
        map.put(DaoConst.FILTER, filterPlus.getParamFilter());
        setEnableFlag(map, true);
        List<Object> list = dataMapper.selectDistinct(map);
        List<V> resultList = Linq4j.asEnumerable(list).select(clazzV::cast).toList();
        if (!CollectionUtils.isEmpty(resultList) && enableCache) {
            dataCache.setEntityDistinct(filter, field, resultList, clazz, table.expireTimeList(), params);
        }
        DaoSourceContextHolder.setDaoSource(DataSourceEnum.DB.getSource());
        return resultList;
    }
}
